////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//         This file is distributed under the BSD 2-Clause open source license. See Licenses/License.txt for details.         //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef __WN_NODE_TYPES_H__
#define __WN_NODE_TYPES_H__

#include "WNScripting/inc/WNTypeElement.h"
#include "WNScripting/inc/WNScriptingErrors.h"
#include "WNScripting/inc/WNCodeModule.h"
#include "WNScripting/inc/WNTypeManager.h"
#include "WNScripting/inc/WNScriptingEnums.h"
#include "WNScripting/inc/WNScopedVariableList.h"
#include "WNScripting/inc/WNScriptLinkedList.h"
#include "WNScripting/inc/WNFunctionParam.h"
#include "WNCore/inc/WNAssert.h"

#ifdef _WN_MSVC
    #pragma warning(push)
    #pragma warning(disable: 4100)
    #pragma warning(disable: 4127)
    #pragma warning(disable: 4152)
    #pragma warning(disable: 4244)
    #pragma warning(disable: 4245)
    #pragma warning(disable: 4267)
    #pragma warning(disable: 4355)
    #pragma warning(disable: 4512)
#endif

#include "llvm/IR/Module.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "WNLogging/inc/WNLog.h"

#ifdef _WN_MSVC
    #pragma warning(pop)
#endif

namespace llvm {
    class Value;
};

namespace WNScripting {
    #define START_OFFSET( x ) for (int _i = 0; _i < x; ++_i ) { printf("    "); }

    inline eWNTypeError GenerateFunctionCall(WNCodeModule& _module, const WNFunctionDefinition* _currentFunction, const WN_CHAR* functionName, const std::vector<FunctionParam>& _funcParams, bool _castable, WNScriptType& _returnType, llvm::Value*& _returnValue, WNLogging::WNLog& _compilationLog) {
        llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
        WNFunctionDefinition* fDefinition;
        std::vector<WNScriptType> scriptTypes;
        for(std::vector<FunctionParam>::const_iterator i = _funcParams.begin(); i != _funcParams.end(); ++i) {
            scriptTypes.push_back(i->mType);
        }
        std::vector<llvm::Value*> arguments;
        eWNTypeError err = eWNOK;

        if(_castable) {
            if(eWNOK != (err = _module.GetCastableFunctionDefinition(functionName, scriptTypes, fDefinition))) {
                _compilationLog.Log(WNLogging::eError, 0, "Function ", functionName, " does not exist with given types");
                return(err);
            }
            for(WN_SIZE_T i = 0; i < _funcParams.size(); ++i) {
                if(_funcParams[i].mType == fDefinition->mTypes[i]) {
                    arguments.push_back(_funcParams[i].mValue);
                } else {
                    const GenerateCastingOperation* castingOp = _module.GetTypeManager().GetCastingOperation(_funcParams[i].mType, fDefinition->mTypes[i]);
                    if(!castingOp) {
                        _compilationLog.Log(WNLogging::eError, 0, "Cannot cast ", _funcParams[i].mType->mName, " to", fDefinition->mTypes[i]->mName);
                        return(eWNInvalidCast);
                    }
                    llvm::Value* castedVal;
                    if(eWNOK != (err = castingOp->Execute(builder, _funcParams[i].mValue, castedVal))) {
                        _compilationLog.Log(WNLogging::eCritical, 0, "Error creating cast ", _funcParams[i].mType->mName, " to", fDefinition->mTypes[i]->mName);
                        return(err);
                    }
                    arguments.push_back(castedVal);
                }
            }
        } else {
            fDefinition = _module.GetFunctionDefinition(functionName, scriptTypes);
            if(!fDefinition) {
                _compilationLog.Log(WNLogging::eError, 0, "Function ", functionName, " does not exist with given types");
                return(eWNPlatformError);
            }
            for(WN_SIZE_T i = 0; i < _funcParams.size(); ++i) {
                arguments.push_back(_funcParams[i].mValue);
            }
        }

        if(fDefinition->mFunction) {
            _returnValue = builder->CreateCall(fDefinition->mFunction, arguments, "");
        } else {
            WNScriptType voidStar = WN_NULL;
            WNScriptType sizeTType = WN_NULL;
            if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("-Ptr", voidStar))) {
                _compilationLog.Log(WNLogging::eCritical, 0, "Cannot find Ptr Type");
                return(err);
            }
            if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("-SizeT", sizeTType))) {
                _compilationLog.Log(WNLogging::eCritical, 0, "Cannot find SizeT Type");
                return(err);
            }


            llvm::Function* f = llvm::Function::Create(fDefinition->mFunctionType, llvm::GlobalValue::ExternalLinkage, "", _module.GetModule());

            llvm::Value* functionPtrLocation = llvm::ConstantInt::get(sizeTType->mLLVMType, reinterpret_cast<size_t>(&(fDefinition->mFunctionPointer)));
            functionPtrLocation = builder->CreateIntToPtr(functionPtrLocation, sizeTType->mLLVMType->getPointerTo(0), "");
            llvm::Value* fPtr = builder->CreateLoad(functionPtrLocation);
            fPtr = builder->CreateIntToPtr(fPtr, sizeTType->mLLVMType->getPointerTo(0));
            fPtr = builder->CreateBitCast(fPtr, f->getType(), "");
            llvm::Value* fPtrStore = builder->CreateAlloca(f->getType());
            builder->CreateStore(fPtr, fPtrStore, false);

            llvm::BasicBlock* doesNotExist = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _currentFunction->mFunction);
            llvm::BasicBlock* end  = llvm::BasicBlock::Create(llvm::getGlobalContext(), "", _currentFunction->mFunction);

            llvm::Value* cmpType = builder->CreatePtrToInt(fPtr, sizeTType->mLLVMType, "");
            llvm::Value* cmpValue = builder->CreateICmpEQ(cmpType, llvm::ConstantInt::get(sizeTType->mLLVMType, 0));
            builder->CreateCondBr(cmpValue, doesNotExist, end);
            builder->SetInsertPoint(doesNotExist);
            
            std::vector<WNScriptType> generateTypes;
            generateTypes.push_back(voidStar);
            generateTypes.push_back(voidStar);
            WNFunctionDefinition* def = _module.GetFunctionDefinition("CompileLazyFunction", generateTypes);
            if(!def) {
                _compilationLog.Log(WNLogging::eCritical, 0, "Cannot find lazy function compiler");
                return(eWNPlatformError);
            }
            llvm::Value* generateValues[2];
            generateValues[0] = builder->CreateIntToPtr(llvm::ConstantInt::get(sizeTType->mLLVMType, reinterpret_cast<size_t>(_module.GetScriptingEngine())), voidStar->mLLVMType, "");
            generateValues[1] = builder->CreateIntToPtr(llvm::ConstantInt::get(sizeTType->mLLVMType, reinterpret_cast<size_t>(fDefinition)), voidStar->mLLVMType, "");
            llvm::Value* fPtrCreated = builder->CreateCall(def->mFunction, llvm::makeArrayRef(generateValues));
            fPtrCreated = builder->CreateBitCast(fPtrCreated, f->getType(), "");
            builder->CreateStore(fPtrCreated, fPtrStore, false);
            builder->CreateBr(end);
            builder->SetInsertPoint(end);

            _returnValue = builder->CreateCall(builder->CreateLoad(fPtrStore), arguments, "");
        }
        _returnType = fDefinition->mReturn;
        return(eWNOK);
    }
   
      WN_INLINE eWNTypeError GenerateVOIDReturn(WNCodeModule& _module, const WNFunctionDefinition* _def) {
        eWNTypeError err = eWNOK;
        llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
        WNScriptType t = WN_NULL;
        if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("Void", t))) {
            return(err);
        }
        if(_def->mReturn != t) {
            return(eWNInvalidCast);
        }
        const GenerateConstantOperation*  constOp = _module.GetTypeManager().GetConstantOperation(t);
        if(!constOp) {
            return(eWNBadType);
        }
        llvm::Value* v = WN_NULL;
        if(eWNOK != (err = constOp->Execute(_module.GetBuilder(), "", v))) {
            return(err);
        }
        builder->CreateRet(v);
        return(eWNOK);
    }
   
    inline eWNTypeError AllocateStruct(WNCodeModule& _module, llvm::Value*& _retVal, WNScriptType structType, const WNFunctionDefinition* _def, WNLogging::WNLog& _compilationLog) {
        llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
        llvm::Value* allocLocation = WN_NULL;
        _retVal = WN_NULL;
        eWNTypeError err = eWNOK;
        const GenerateAllocation* alloc = _module.GetTypeManager().GetAllocationOperation(structType);
        if(!alloc) {
            return(eWNCannotCreateType);
        }
        if(eWNOK != (err = alloc->Execute(_module, structType, "", false, allocLocation))) {
            return(err);
        }
    #ifdef _WN_64_BIT
        llvm::Type* ptrType = llvm::IntegerType::getInt64Ty(llvm::getGlobalContext());
    #else
        llvm::Type* ptrType = llvm::IntegerType::getInt32Ty(llvm::getGlobalContext());
    #endif
        WNScriptType sizeTType = 0;
        if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("-SizeT", sizeTType))) {
            return(err);
        }
    
        std::vector<WNScriptType> mallocTypes;
        mallocTypes.push_back(sizeTType);
        WNFunctionDefinition* def = _module.GetFunctionDefinition("Malloc", mallocTypes);
        if(!def) {
            return(eWNPlatformError);
        }

        std::vector<llvm::Value*> vals;
        vals.push_back(llvm::ConstantInt::get(sizeTType->mLLVMType, _module.GetExecutionEngine()->getDataLayout()->getTypeAllocSize(allocLocation->getType()->getContainedType(0)->getContainedType(0))));
        llvm::Value* v = builder->CreateCall(def->mFunction, vals, "");
        v = builder->CreatePointerCast(v, allocLocation->getType()->getContainedType(0));

        vals.clear();
        vals.push_back(llvm::ConstantInt::get(sizeTType->mLLVMType, _module.GetExecutionEngine()->getDataLayout()->getTypeAllocSize(structType->mLLVMStructType)));
        llvm::Value* actualStruct = builder->CreateCall(def->mFunction, vals, "");
        actualStruct = builder->CreatePointerCast(actualStruct, structType->mLLVMStructType->getPointerTo(0));

        std::vector<llvm::Value*> GepArray;
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 1));
        builder->CreateStore(actualStruct, builder->CreateGEP(v, GepArray));

        GepArray.clear();
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 2));
        builder->CreateStore(llvm::ConstantInt::get(ptrType, 0), builder->CreateGEP(v, GepArray));

        GepArray.clear();
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        ///builder->CreateGEP(v, GepArray);
        builder->CreateStore(builder->CreateIntToPtr(llvm::ConstantInt::get(sizeTType->mLLVMType, 0), llvm::IntegerType::getInt32PtrTy(llvm::getGlobalContext())), builder->CreateGEP(v, GepArray));

        WN_SIZE_T nameLen = WNStrings::WNStrLen(structType->mName);
        WN_CHAR *functionName = static_cast<WN_CHAR*>(WN_STACK_ALLOC(sizeof(WN_CHAR)* (nameLen + 1 + 5)));
        WNMemory::WNMemCpy(functionName, structType->mName, nameLen);
        WNMemory::WNMemCpy(functionName + nameLen, "Const", 5);
        functionName[nameLen + 5] = '\0';
        std::vector<FunctionParam> fParams;
        fParams.push_back(FunctionParam());
        fParams.back().mType = structType;
        fParams.back().mValue = v;

        WNScriptType returnType;
        llvm::Value* returnValue;
        if(eWNOK != (err = GenerateFunctionCall(_module, _def, functionName, fParams, false, returnType, returnValue, _compilationLog))) {
            return(err);
        }
        
        builder->CreateStore(v, allocLocation);
        _retVal = v;        
        return(eWNOK);
    }
    inline  eWNTypeError GenerateNullAllocation(WNCodeModule& _module, const WNFunctionDefinition* /*_def*/, llvm::Value*& _retVal, WNScriptType& _outType) {
        llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>*>(_module.GetBuilder());
        eWNTypeError err = eWNOK;
        _retVal = NULL;
        if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("-Null", _outType))) {
            return(err);
        }
            
        llvm::Value* allocLocation = WN_NULL;
        const GenerateAllocation* alloc = _module.GetTypeManager().GetAllocationOperation(_outType);
        if(!alloc) {
            return(eWNCannotCreateType);
        }
        if(eWNOK != (err = alloc->Execute(_module, _outType, "", false, allocLocation))) {
            return(err);
        }
#ifdef _WN_64_BIT
        llvm::Type* ptrType = llvm::IntegerType::getInt64Ty(llvm::getGlobalContext());
#else
        llvm::Type* ptrType = llvm::IntegerType::getInt32Ty(llvm::getGlobalContext());
#endif
        WNScriptType sizeTType = 0;
        if(eWNOK != (err = _module.GetTypeManager().GetTypeByName("-SizeT", sizeTType))) {
            return(err);
        }
    
        std::vector<WNScriptType> mallocTypes;
        mallocTypes.push_back(sizeTType);
        WNFunctionDefinition* def = _module.GetFunctionDefinition("Malloc", mallocTypes);
        if(!def) {
            return(eWNPlatformError);
        }

        std::vector<llvm::Value*> vals;
        vals.push_back(llvm::ConstantInt::get(sizeTType->mLLVMType, _module.GetExecutionEngine()->getDataLayout()->getTypeAllocSize(allocLocation->getType()->getContainedType(0)->getContainedType(0))));
        llvm::Value* v = builder->CreateCall(def->mFunction, vals, "");
        v = builder->CreatePointerCast(v, allocLocation->getType()->getContainedType(0));

        std::vector<llvm::Value*> GepArray;
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 1));
        builder->CreateStore(builder->CreateIntToPtr(llvm::ConstantInt::get(sizeTType->mLLVMType, 0), _outType->mLLVMStructType->getPointerTo(0)), builder->CreateGEP(v, GepArray));

        GepArray.clear();
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 2));
        builder->CreateStore(llvm::ConstantInt::get(ptrType, 0), builder->CreateGEP(v, GepArray));

        GepArray.clear();
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        GepArray.push_back(llvm::ConstantInt::get(llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()), 0));
        ///builder->CreateGEP(v, GepArray);
        builder->CreateStore(builder->CreateIntToPtr(llvm::ConstantInt::get(sizeTType->mLLVMType, 0), llvm::IntegerType::getInt32PtrTy(llvm::getGlobalContext())), builder->CreateGEP(v, GepArray));

        builder->CreateStore(v, allocLocation);
        _retVal = v;
        return(eWNOK);
    }

}
#endif//__WN_NODE_TYPES_H__
